structure MipsGen : CODEGEN =
struct
    structure Frame = MipsFrame

    structure E = ErrorMsg
    structure F = MipsFrame
    structure I = Assem
    structure M = Temp
    structure R = Tree
    structure S = Symbol
    structure T = Temp
		      
    fun codegen (frame as {name, formals, size, moves, saves}) stm =
	let val ilist = ref []
			
	    fun int z = if (z<0)
			then "-" ^ (Int.toString (0 - z))
			else Int.toString z
		      
	    fun emit x = ilist := x :: !ilist
			 
	    fun result gen = let val t = T.newtemp()
			     in ( gen t
				; t
				)
			     end
			     
	    val calldefs = F.calldefs
			   
	    fun munchStm (R.SEQ (s, s')) = ( munchStm s
					   ; munchStm s' )
					   
	      | munchStm (R.LABEL l) = emit (I.LABEL { assem= (S.name l) ^ ":\n",
						       lab=l })
				       
	      | munchStm (R.JUMP (e, ls)) = emit (I.OPER { assem="jr `s0\n",
							   src=[munchExp e],
							   dst=[],
							   jump=SOME ls })
					    
	      | munchStm (R.CJUMP (R.EQ, e, e', l, l'))  = emit (I.OPER { assem="beq `s0, `s1, " ^ (S.name l) ^ "\n",
									  src=[munchExp e, munchExp e'],
									  dst=[],
									  jump=SOME [l, l'] })
							   
	      | munchStm (R.CJUMP (R.NE, e, e', l, l')) = emit (I.OPER { assem="bne `s0, `s1, " ^ (S.name l) ^ "\n",
									 src=[munchExp e, munchExp e'],
									 dst=[],
									 jump=SOME [l, l'] })
							  
	      | munchStm (R.CJUMP (R.LT, e, e', l, l'))  = emit (I.OPER { assem="blt `s0, `s1, " ^ (S.name l) ^ "\n",
									  src=[munchExp e, munchExp e'],
									  dst=[],
									  jump=SOME [l, l'] })
							   
	      | munchStm (R.CJUMP (R.GT, e, e', l, l'))  = emit (I.OPER { assem="bgt `s0, `s1, " ^ (S.name l) ^ "\n",
									  src=[munchExp e, munchExp e'],
									  dst=[],
									  jump=SOME [l, l'] })
							   
	      | munchStm (R.CJUMP (R.LE, e, e', l, l'))  = emit (I.OPER { assem="ble `s0, `s1, " ^ (S.name l) ^ "\n",
									  src=[munchExp e, munchExp e'],
									  dst=[],
									  jump=SOME [l, l'] })
							   
	      | munchStm (R.CJUMP (R.GE, e, e', l, l'))  = emit (I.OPER { assem="bge `s0, `s1, " ^ (S.name l) ^ "\n",
									  src=[munchExp e, munchExp e'],
									  dst=[],
									  jump=SOME [l, l'] })
							   
	      | munchStm (R.CJUMP (R.ULT, e, e', l, l')) = emit (I.OPER { assem="bltu `s0, `s1, " ^ (S.name l) ^ "\n",
									  src=[munchExp e, munchExp e'],
									  dst=[],
									  jump=SOME [l, l'] })
							   
	      | munchStm (R.CJUMP (R.UGT, e, e', l, l')) = emit (I.OPER { assem="bgtu `s0, `s1, " ^ (S.name l) ^ "\n",
									  src=[munchExp e, munchExp e'],
									  dst=[],
									  jump=SOME [l, l'] })
							   
	      | munchStm (R.CJUMP (R.ULE, e, e', l, l')) = emit (I.OPER { assem="bleu `s0, `s1, " ^ (S.name l) ^ "\n",
									  src=[munchExp e, munchExp e'],
									  dst=[],
									  jump=SOME [l, l'] })
							   
	      | munchStm (R.CJUMP (R.UGE, e, e', l, l')) = emit (I.OPER { assem="bgeu `s0, `s1, " ^ (S.name l) ^ "\n",
									  src=[munchExp e, munchExp e'],
									  dst=[],
									  jump=SOME [l, l'] })
							   
	      | munchStm (R.MOVE (R.MEM e, e')) = emit (I.OPER { assem="sw `s1, (`s0)\n",
								 src=[munchExp e, munchExp e'],
								 dst=[],
								 jump=NONE })
						  
	      | munchStm (R.MOVE (R.TEMP t, R.MEM e)) = emit (I.OPER { assem="lw `d0, (`s0)\n",
								 src=[munchExp e],
								 dst=[t],
								 jump=NONE })
						  
	      | munchStm (R.MOVE (R.TEMP t, e)) = emit (I.MOVE { assem="move `d0, `s0\n",
								 src=(munchExp e),
								 dst=t })
						  
	      | munchStm (R.EXP e) = ignore (munchExp e)
				     
	      | munchStm _ = E.impossible "CODEGEN STM MISMATCH"
			     
			     
	    and munchExp (R.BINOP (R.PLUS, e, e')) = result (fn r => emit (I.OPER { assem="add `d0, `s0, `s1\n",
										    src=[munchExp e, munchExp e'],
										    dst=[r],
										    jump=NONE }))
						     
	      | munchExp (R.BINOP (R.MINUS, e, e')) = result (fn r => emit (I.OPER { assem="sub `d0, `s0, `s1\n",
										     src=[munchExp e, munchExp e'],
										     dst=[r],
										     jump=NONE }))
						      
	      | munchExp (R.BINOP (R.MUL, e, e')) = result (fn r => emit (I.OPER { assem="mul `d0, `s0, `s1\n", (* NB pseudo instruction *)
										   src=[munchExp e, munchExp e'],
										   dst=[r],
										   jump=NONE }))
						    
	      | munchExp (R.BINOP (R.DIV, e, e')) = result (fn r => emit (I.OPER { assem="div `d0, `s0, `s1\n", (* NB pseudo instruction *)
										   src=[munchExp e, munchExp e'],
										   dst=[r],
										   jump=NONE }))
						    
	      | munchExp (R.BINOP (R.AND, e, e')) = result (fn r => emit (I.OPER { assem="and `d0, `s0, `s1\n",
										   src=[munchExp e, munchExp e'],
										   dst=[r],
										   jump=NONE }))
						    
	      | munchExp (R.BINOP (R.OR, e, e')) = result (fn r => emit (I.OPER { assem="or `d0, `s0, `s1\n",
										  src=[munchExp e, munchExp e'],
										  dst=[r],
										  jump=NONE }))
						   
	      | munchExp (R.BINOP (R.LSHIFT, e, e')) = result (fn r => emit (I.OPER { assem="sll `d0, `s0, `s1\n",
										      src=[munchExp e, munchExp e'],
										      dst=[r],
										      jump=NONE }))
						       
	      | munchExp (R.BINOP (R.RSHIFT, e, e')) = result (fn r => emit (I.OPER { assem="srl `d0, `s0, `s1\n",
										      src=[munchExp e, munchExp e'],
										      dst=[r],
										      jump=NONE }))
						       
	      | munchExp (R.BINOP (R.ARSHIFT, e, e')) = result (fn r => emit (I.OPER { assem="sra `d0, `s0, `s1\n",
										       src=[munchExp e, munchExp e'],
										       dst=[r],
										       jump=NONE }))
							
	      | munchExp (R.BINOP (R.XOR, e, e')) = result (fn r => emit (I.OPER { assem="xor `d0, `s0, `s1\n",
										   src=[munchExp e, munchExp e'],
										   dst=[r],
										   jump=NONE }))
						    
	      | munchExp (R.MEM e) = result (fn r => emit (I.OPER { assem="lw `d0, (`s0)\n",
								    src=[munchExp e],
								    dst=[r],
								    jump=NONE }))
				     
	      | munchExp (R.TEMP t) = t
				      
	      | munchExp (R.ESEQ (s, e)) = ( munchStm s
					   ; munchExp e )
					   
	      | munchExp (R.NAME l) = result (fn r => emit (I.OPER { assem="la `d0, " ^ (S.name l) ^ "\n",
								     src=[],
								     dst=[r],
								     jump=NONE }))
				      
	      | munchExp (R.CONST i) = result (fn r => emit (I.OPER { assem="li `d0, " ^ (int i) ^ "\n",
								      src=[],
								      dst=[r],
								      jump=NONE }))
				       
	      | munchExp (R.CALL (f, es)) = result (fn r => ( if name = F.mainLabel then ()
							      else ListPair.appEq (fn (r, m) => munchStm (R.MOVE (m, R.TEMP r)))
										  (F.callersaves,
										   saves)
								   handle ListPair.UnequalLengths => E.impossible "CODGEN: wrong number of caller-saves vs locations"
							    ; emit (I.OPER { assem="jal `s0\n",
									     src=(munchExp f)::(munchArgs es),
									     dst=r::calldefs,
									     jump=NONE })
							    ; emit (I.MOVE { assem= "move `d0, `s0\n",
									     src=F.RV,
									     dst=r })
							    ; if name = F.mainLabel then ()
							      else ListPair.appEq (fn (r, m) => munchStm (R.MOVE (R.TEMP r, m)))
										  (F.callersaves,
										   saves)
								   handle ListPair.UnequalLengths => E.impossible "CODGEN: wrong number of caller-saves vs locations"
							    )
						   )
					    
	    and munchArgs es = ListPair.map (fn (t, e) => let val src = munchExp e
							  in ( emit (I.MOVE { assem="move `d0, `s0\n",
									      src=src,
									      dst=t })
							     ; src)
							  end)
					    (F.args, es)
		               handle ListPair.UnequalLengths => E.impossible "CALL: MORE ARGS THAN REGISTERS"

	in ( munchStm stm
	   ; rev (!ilist)
	   )
	end
end
